using System;
using System.Diagnostics;

public class CircularBuffer
{
    private byte[] m_base;
    private ulong m_size;
    private ulong m_in;
    private ulong m_out;

    private CircularBuffer() { }

    public CircularBuffer(ulong size)
    {
        m_base = new byte[size];
        m_size = size;
        Debug.Assert((size & (size - 1)) == 0);
    }

    public byte[] GetBuffer()
    {
        return m_base;
    }

    public bool IsEmpty()
    {
        return m_in == m_out;
    }

    public bool IsFull()
    {
        return m_in == m_out + m_size;
    }

    public ulong GetWritableSpace()
    {
        return m_out + m_size - m_in;
    }

    public ulong GetReadableSpace()
    {
        return m_in - m_out;
    }

    public int Write(byte[] data, int offset = 0, int count = -1)
    {
        int n = 0;
        if (count == -1)
        {
            count = data.Length - offset;
        }
        for (var i = 0; i < 2 && count > 0 && !IsFull(); ++i)
        {
            var size = Math.Min((int)GetContiguiousWritableSpace(), count);
            Buffer.BlockCopy(data, offset, m_base, (int)GetContiguiousWritableOffset(), size);
            IncrementContiguiousWritten((ulong)size);
            offset += size;
            count -= size;
            n += size;
        }
        return n;
    }

    public int Read(byte[] buffer, int offset = 0, int count = -1)
    {
        int n = 0;
        if (count == -1)
        {
            count = buffer.Length - offset;
        }
        for (var i = 0; i < 2 && count > 0 && !IsEmpty(); ++i)
        {
            var size = Math.Min((int)GetContiguiousReadableSpace(), count);
            Buffer.BlockCopy(m_base, (int)GetContiguiousReadableOffset(), buffer, offset, size);
            IncrementContiguiousRead((ulong)size);
            offset += size;
            count -= size;
            n += size;
        }
        return n;
    }

    public int Remove(int count)
    {
        int n = 0;
        for (var i = 0; i < 2 && count > 0 && !IsEmpty(); ++i)
        {
            var size = Math.Min((int)GetContiguiousReadableSpace(), count);
            IncrementContiguiousRead((ulong)size);
            count -= size;
            n += size;
        }
        return n;
    }

    public int Peek(byte[] buffer, int offset = 0, int count = -1)
    {
        var other = new CircularBuffer
        {
            m_base = m_base,
            m_size = m_size,
            m_in = m_in,
            m_out = m_out
        };
        return other.Read(buffer, offset, count);
    }

    public ulong GetContiguiousWritableSpace()
    {
        return Math.Min(m_out + m_size - m_in, m_size - (m_in & (m_size - 1)));
    }

    public ulong GetContiguiousWritableOffset()
    {
        return m_in & (m_size - 1);
    }

    public void IncrementContiguiousWritten(ulong size)
    {
        Debug.Assert(size <= Math.Min(m_out + m_size - m_in, m_size - (m_in & (m_size - 1))));
        m_in += size;
    }

    public ulong GetContiguiousReadableSpace()
    {
        return Math.Min(m_in - m_out, m_size - (m_out & (m_size - 1)));
    }

    public ulong GetContiguiousReadableOffset()
    {
        return m_out & (m_size - 1);
    }

    public void IncrementContiguiousRead(ulong size)
    {
        Debug.Assert(size <= Math.Min(m_in - m_out, m_size - (m_out & (m_size - 1))));
        m_out += size;
    }
}
